﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Pg.BioMedics.SDR.Entities;
using Pg.BioMedics.SDR.Entities.Codes;
using Pg.BioMedics.SDR.Data;

namespace Pg.BioMedics.SDR.Services.Impl
{
    /// <summary>
    /// IUserManagementService interface implementation. This class provides logic
    /// for person accounts managment.
    /// </summary>
    internal class ManagementService : IManagementService
    {
        #region IPersonManagementService Members

        /// <summary>
        /// Contains logic for person account registration without system roles
        /// assignment
        /// </summary>
        /// <param name="person">Person account</param>
        /// <returns>Saved person account</returns>
        public Person Register(Person person)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not register person, person can not be null");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    // create and activate account
                    person = dataContext.PersonManager.InsertPerson(person);
                    dataContext.PersonManager.SetPersonStatus(person, PersonStatusCodes.Registered);

                    // commit transaction
                    dataContext.Complete();

                    person.StatusCode = PersonStatusCodes.Registered;
                    person.StatusDate = DateTime.Now;
                    return person;
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not register person {0} {1}", ex, person.Name, person.Surname);
            }
        }

        /// <summary>
        /// Contains logic for new account registeration and valid
        /// played roles assignment
        /// </summary>
        /// <param name="person">Saved person instance</param>
        /// <param name="playedRoles">List of played roles</param>
        /// <returns>Registered person</returns>
        public Person Register(Person person, params PlayedRole[] playedRoles)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not register person, person can not be null");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    //register and activate person
                    person = dataContext.PersonManager.InsertPerson(person);
                    dataContext.PersonManager.SetPersonStatus(person, PersonStatusCodes.Registered);

                    // assign roles
                    foreach (PlayedRole playedRole in playedRoles)
                    {
                        playedRole.PersonId = person.Id.Value;

                        PlayedRole internalPlayedRole = dataContext.PersonManager.InsertPlayedRole(playedRole);
                        dataContext.PersonManager.SetPlayedRoleStatus(internalPlayedRole, PlayedRoleStatusCodes.Active);
                    }

                    // commit transaction and return result
                    dataContext.Complete();

                    person.StatusCode = PersonStatusCodes.Registered;
                    person.StatusDate = DateTime.Now;
                    return person;
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not register person {0} {1} and assign roles", ex, person.Name, person.Surname);
            }
        }

        /// <summary>
        /// Provides logic for person account data update. This method does not reassign
        /// roles played by the person.
        /// </summary>
        /// <param name="person">Existing person account</param>
        public void Update(Person person)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not update person, person can not be null");
            else if (person.Id == null)
                throw new ServiceException("Could not update person, person must be saved first");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    // update
                    dataContext.PersonManager.UpdatePerson(person);

                    // commit transaction
                    dataContext.Complete();
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not update person account {0}", ex, person.Id);
            }
        }

        /// <summary>
        /// Provides logic for person account update and played roles assignment
        /// </summary>
        /// <param name="person">Person instance</param>
        /// <param name="playedRoles">List of played roles</param>
        public void Update(Person person, params PlayedRole[] playedRoles)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not update person, person can not be null");
            else if (person.Id == null)
                throw new ServiceException("Could not update person, person must be saved first");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    // update person account
                    dataContext.PersonManager.UpdatePerson(person);

                    // reassign roles
                    IEnumerable<PlayedRole> removedRoles = from playedRole in dataContext.PersonManager.GetPlayedRoles(person, PlayedRoleStatusCodes.Active)
                                                           where (playedRole.StartDate <= DateTime.Now) &&
                                                                 (playedRole.EndDate >= DateTime.Now) &&
                                                                 (!playedRoles.Contains(playedRole, new Linq.EqualityComparer<PlayedRole> { Comparer = (r1, r2) => r1.Id == r2.Id , Hasher = r => r.Id.Value }))
                                                           select playedRole;

                    IEnumerable<PlayedRole> addedRoles = from playedRole in playedRoles
                                                         let databaseRoles = (from databaseRole in dataContext.PersonManager.GetPlayedRoles(person, PlayedRoleStatusCodes.Active)
                                                                              where (databaseRole.StartDate <= DateTime.Now) &&
                                                                                    (databaseRole.EndDate >= DateTime.Now)
                                                                              select databaseRole)
                                                         where (!databaseRoles.Contains(playedRole, new Linq.EqualityComparer<PlayedRole> { Comparer = (r1, r2) => r1.Id == r2.Id, Hasher = r => r.Id.Value }))
                                                         select playedRole;

                    // remove removed roles
                    foreach (PlayedRole removedRole in removedRoles)
                        dataContext.PersonManager.SetPlayedRoleStatus(removedRole, PlayedRoleStatusCodes.Disabled);

                    // add added roles
                    foreach (PlayedRole addedRole in addedRoles)
                    {
                        addedRole.PersonId = person.Id.Value;

                        PlayedRole internalAddedRole = dataContext.PersonManager.InsertPlayedRole(addedRole);
                        dataContext.PersonManager.SetPlayedRoleStatus(internalAddedRole, PlayedRoleStatusCodes.Active);
                    }

                    // commit transaction
                    dataContext.Complete();
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not update person account {0} with roles reassginment", ex, person.Id);
            }
        }

        /// <summary>
        /// Provides logic for person account disabling
        /// </summary>
        /// <param name="person">Existing person account</param>
        public void Disable(Person person)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not disable person, person can not be null");
            else if (person.Id == null)
                throw new ServiceException("Could not disable person, person must be saved first");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    // set person status
                    dataContext.PersonManager.SetPersonStatus(person, PersonStatusCodes.Disabled);

                    person.StatusCode = PersonStatusCodes.Disabled;
                    person.StatusDate = DateTime.Now;

                    // commit changes
                    dataContext.Complete();
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not disable person {0}", ex, person.Id);
            }
        }

        /// <summary>
        /// Provides logic for person account activating
        /// </summary>
        /// <param name="person">Existing person account</param>
        public void Activate(Person person)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not activate person, person can not be null");
            else if (person.Id == null)
                throw new ServiceException("Could not activate person, person must be saved first");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    // set person status
                    dataContext.PersonManager.SetPersonStatus(person, PersonStatusCodes.Active);

                    person.StatusCode = PersonStatusCodes.Active;
                    person.StatusDate = DateTime.Now;

                    // commit changes
                    dataContext.Complete();
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not actovate person {0}", ex, person.Id);
            }
        }

        /// <summary>
        /// Provides logic for person account activation 
        /// and password assignment
        /// </summary>
        /// <param name="person">person</param>
        /// <param name="password">person password</param>
        public void Activate(Person person, string password)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not activate person, person can not be null");
            else if (person.Id == null)
                throw new ServiceException("Could not activate person, person must be saved first");
            else if (String.IsNullOrEmpty(password))
                throw new ServiceException("Could not activate person {0}, password can not be null or empty string", person.Id);

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext())
                {
                    // set password
                    dataContext.PersonManager.SetPassword(person, password);

                    // set person status
                    dataContext.PersonManager.SetPersonStatus(person, PersonStatusCodes.Active);

                    person.StatusCode = PersonStatusCodes.Active;
                    person.StatusDate = DateTime.Now;

                    // commit changes
                    dataContext.Complete();
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not actovate person {0}", ex, person.Id);
            }
        }

        /// <summary>
        /// Provides logic for getting person by unique id
        /// </summary>
        /// <param name="personId">Person unique id</param>
        /// <returns>Person or null if not exists</returns>
        public Person GetPersonById(int personId)
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPersonById(personId);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get person by id {0}", ex, personId);
            }
        }

        /// <summary>
        /// Provides logic for getting person by its login
        /// </summary>
        /// <param name="login">Person login</param>
        /// <returns>Person or null if login doesnot exist</returns>
        public Person GetPersonByLogin(string login)
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPersonByLogin(login);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get person by login {0}", ex, login);
            }
        }

        /// <summary>
        /// Provides logic for retriving accounts with active operator role
        /// </summary>
        /// <returns>List of active operators</returns>
        public IList<Person> GetOperators()
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPeopleInRole(RoleCodes.Operational, PlayedRoleStatusCodes.Active);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get list of operators", ex);
            }
        }

        /// <summary>
        /// Provides logic for retriving accounts with active administrator role
        /// </summary>
        /// <returns>List of administrators</returns>
        public IList<Person> GetAdministrators()
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPeopleInRole(RoleCodes.Administrator, PlayedRoleStatusCodes.Active);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get list of administrators", ex);
            }
        }

        /// <summary>
        /// Provides logic for retriving accounts with external operational party role
        /// </summary>
        /// <returns>List of external perties</returns>
        public IList<Person> GetExternals()
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPeopleInRole(RoleCodes.ExternalOperational, PlayedRoleStatusCodes.Active);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get list of external operators", ex);
            }
        }

        /// <summary>
        /// Gets list of all active accounts independently of 
        /// their played roles
        /// </summary>
        /// <returns>List of people</returns>
        public IList<Person> GetActives()
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPeopleByStatus(PersonStatusCodes.Active);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get list of active accounts", ex);
            }
        }

        /// <summary>
        /// provides logic for retriving list of inactive accounts
        /// </summary>
        /// <returns>List of inactive accounts</returns>
        public IList<Person> GetInActives()
        {
            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    IList<Person> registeredAccounts = dataContext.PersonManager.GetPeopleByStatus(PersonStatusCodes.Registered);
                    IList<Person> disabledAccounts = dataContext.PersonManager.GetPeopleByStatus(PersonStatusCodes.Disabled);

                    return registeredAccounts.Union(disabledAccounts)
                                             .ToList();
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get list of inactive accounts", ex);
            }
        }

        /// <summary>
        /// Gets list of active played roles assigned to the person
        /// </summary>
        /// <param name="person">Existsing person</param>
        /// <returns>List of played roles</returns>
        public IList<PlayedRole> GetPlayedRoles(Person person)
        {
            // input params validation
            if (person == null)
                throw new ServiceException("Could not get list of played roles for person, person can not be null");
            else if (person.Id == null)
                throw new ServiceException("Could not get list of played roels for person, person must be saved first");

            try
            {
                using (SDRDataContext dataContext = new SDRDataContext(false))
                {
                    return dataContext.PersonManager.GetPlayedRoles(person, PlayedRoleStatusCodes.Active);
                }
            }
            catch (Exception ex)
            {
                throw new ServiceException("Could not get list of played roles for person {0}", ex, person.Id);
            }
        }

        #endregion
    }
}
